﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;

namespace MoveNext.IO.Grf
{
    public class GrfReader : BaseReader
    {
        private Encoding encoding;
        private GrfHeader grfHeader;

        public GrfHeader GrfHeader
        {
            get { return grfHeader; }
        }
        private FileTableHeader fileTableHeader;

        public FileTableHeader FileTableHeader
        {
            get { return fileTableHeader; }
        }

        public GrfReader(Stream input)
            : this(input, GrfConst.Encoding)
        {
        }

        public GrfReader(Stream input, Encoding encoding)
            : base(input)
        {
            this.BaseStream.Seek(0, SeekOrigin.Begin);
            this.encoding = encoding;
            this.grfHeader = this.ReadHeader();
            this.fileTableHeader = this.ReadFileTableHeader(grfHeader.FileTableOffset);
        }

        public virtual void CreateFile(FileEntry fileEntry, string destinationDirectory, bool useGrfFolder, bool overwrite)
        {
            byte[] cdata, data;
            FileInfo fi = new FileInfo(destinationDirectory + "\\" + (useGrfFolder ? fileEntry.FullName : fileEntry.FileName));
            if (!fi.Directory.Exists)
            {
                fi.Directory.Create();
            }
            using (FileStream fs = new FileStream(fi.FullName, overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.Write))
            {
                this.BaseStream.Seek(fileEntry.Offset, SeekOrigin.Begin);
                cdata = this.ReadBytes(fileEntry.CompressedLength);
                using (InflaterInputStream iis = new InflaterInputStream(new MemoryStream(cdata)))
                {
                    data = new byte[fileEntry.UncompressedLength];
                    iis.Read(data, 0, data.Length);
                }
                fs.Write(data, 0, data.Length);
            }
        }

        public virtual FileEntry[] ReadFileEntrys()
        {
            this.BaseStream.Seek(this.fileTableHeader.TableDataOffset, SeekOrigin.Begin);
            long pos = this.BaseStream.Position;
            List<FileEntry> fileEntrys = new List<FileEntry>();
            byte[] compressed = this.ReadBytes(this.fileTableHeader.CompressedLength);
            byte[] uncompressed = new byte[this.fileTableHeader.UncompressedLength];

            using (InflaterInputStream iis = new InflaterInputStream(new MemoryStream(compressed)))
            {
                iis.Read(uncompressed, 0, uncompressed.Length);
            }

            using (GrfFileEntryReader gfer = new GrfFileEntryReader(new MemoryStream(uncompressed)))
            {
                for (int i = 0; i < grfHeader.Entrys; i++)
                {
                    fileEntrys.Add(gfer.ReadFileEntry());
                }
            }

            return fileEntrys.ToArray();
        }

        protected FileTableHeader ReadFileTableHeader(long offset)
        {
            return this.ReadFileTableHeader(offset, SeekOrigin.Begin);
        }

        protected virtual FileTableHeader ReadFileTableHeader(long offset, SeekOrigin origin)
        {
            FileTableHeader header = new FileTableHeader();
            this.BaseStream.Seek(offset, origin);
            header.CompressedLength = this.ReadInt32();
            header.UncompressedLength = this.ReadInt32();
            header.TableDataOffset = this.BaseStream.Position;

            return header;
        }

        protected virtual GrfHeader ReadHeader()
        {
            GrfHeader header = new GrfHeader();
            header.Sig = this.ReadString(16);
            header.Crypt = this.ReadChars(14);
            header.FileTableOffset = this.ReadInt32() + GrfConst.GRF_HEADER_SIZE;
            header.Num = this.ReadInt32();
            header.Entrys = this.ReadInt32() - 7;// dont know why -7
            header.Version = (GrfVersion)this.ReadInt32();

            if (header.Num > header.Entrys || header.Sig != "Master of Magic\0")
            {
                throw new GrfException("Invalid header.");
            }

            return header;
        }
    }
}
